package se.webbzon.boltzmann.game.sequence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import se.webbzon.boltzmann.debug.Debug;
import se.webbzon.boltzmann.environment.AbstractEnvironment;
import se.webbzon.boltzmann.environment.VariableMap;
import se.webbzon.boltzmann.game.event.SetGameCameraTargetEvent;
import se.webbzon.boltzmann.game.sequence.builder.AbstractSequenceBinding;
import se.webbzon.boltzmann.input.Input;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldobject.WorldObject;

public class Sequence {
	
	// A collection of all the listener to this sequence
	private final Collection<SequenceListener> listeners;
	
	// The abstract environment in which the sequence is running
	private final AbstractEnvironment env;
	
	// The world area in which sequence is executed 
	private final WorldArea world;
	
	// The stack trace of this sequence
	private final Stack<SequenceNode> sequence;
	
	// A binding map between a sequence node and an instance
	private final Map<SequenceNode,WorldObject> hardBindings;
	private final Map<SequenceNode,String> softBindings;
	private final Map<String,WorldObject> bindings;
	
	// The local variable map for this sequence
	private VariableMap localVariables;
	
	// The current active node
	private SequenceNode currentNode;
	
	// Whatever this sequence is busy or not
	private boolean busy;
	
	// The input associated with this sequence
	private Input input;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new sequence in the provided environment and
	 * world area. **/
	public Sequence(AbstractEnvironment env, WorldArea world) {
		this.env = env;
		this.world = world;
		listeners = new ArrayList<SequenceListener>();
		sequence = new Stack<SequenceNode>();
		hardBindings = new HashMap<SequenceNode,WorldObject>();
		softBindings = new HashMap<SequenceNode,String>();
		bindings = new HashMap<String,WorldObject>();
		busy = false;
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Adds a listener to this sequence. **/
	public boolean addListener(SequenceListener listener) {
		int d;
		Debug.debugCollection(this, listeners);
		return listeners.add(listener);
	}
	
	/** Removes a listener from this sequence. **/
	public boolean removeListener(SequenceListener listener) {
		return listeners.remove(listener);
	}
	
	/** Clears all the listeners from this sequence. **/
	public void clearListeners() {
		listeners.clear();
	}
	
	/** Returns the abstract environment associated with this sequence. **/
	public AbstractEnvironment getEnvironment() {
		return env;
	}
	
	/** Returns the world area in which this sequence is executed. **/
	public WorldArea getWorld() {
		return world;
	}
	
	/** Returns the stack trace of this sequence. **/
	public Stack<SequenceNode> getStack() {
		return sequence;
	}
	
	/** Adds a new hard binding between a sequence node and an instance. **/
	public void addBinding(SequenceNode node, WorldObject instance) {
		int d;
		Debug.debugMap(this, hardBindings);
		hardBindings.put(node, instance);
	}
	
	/** Adds a new soft binding between a sequence node and a string. **/
	public void addBinding(SequenceNode node, String bindingName) {
		int d;
		Debug.debugMap(this, softBindings);
		softBindings.put(node, bindingName);
	}
	
	/** Binds the provided instance to the soft binding with the given 
	 * binding name. **/
	public void bindTo(String bindingName, WorldObject instance) {
		if (bindingName != null && instance != null) {
			bindings.put(bindingName, instance);
			int d;
			Debug.debugMap(this, bindings);
		}
	}
	
	/** Returns the instance associated with the given node. If
	 * no instance is associated with the given node then null is
	 * returned.**/
	public WorldObject getBinding(SequenceNode node) {
		WorldObject instance = hardBindings.get(node);
		if (instance == null) {
			String key = softBindings.get(node);
			if (key == null)
				return null;
			else
				return bindings.get(key);
		} else
			return instance;
	}
	
	/** Returns the instance associated with the given soft binding
	 * name. If no instance is associated with the given soft binding
	 * name then null is returned. **/
	public WorldObject getBinding(String softBinding) {
		return bindings.get(softBinding);
	}
	
	/** Returns true if the sequence was started. If the sequence
	 * is already running then false is returned. A map may
	 * be provided when calling this method, linking strings to 
	 * instances. If there are no dynamic bindings in the nodes
	 * of this sequence then null may be provided.  **/
	public boolean begin(Input input, SequenceNode first, Map<String,WorldObject> bindings) {
		if (busy)
			return false;
		else {
			currentNode = first;
			busy = true;
			this.input = input;
			if (bindings != null)
				this.bindings.putAll(bindings);
			localVariables = new VariableMap();
			for (SequenceListener listener : listeners.toArray(new SequenceListener[0]))
				listener.onSequenceBegin(this);
			first.begin();
			return true;
		}
	}
	
	/** Continues the sequence by terminating the current sequence
	 * node and starts the given branch for the current node. If the
	 * given branch if negative or smaller than or equal to the number
	 * of branches of the current sequence node then the sequence is
	 * terminated. The sequence is also terminated if a branch is
	 * defined as null. If the sequence has not been started then 
	 * false is returned, otherwise true is returned. **/
	public boolean next(int branch) {
		if (!busy)
			return false;
		
		if (branch < 0 || currentNode.branches() <= branch)
			end();
		else {
			SequenceNode next = currentNode.getBranch(branch);
			if (next == null)
				end();
			else {
				currentNode.end();
				sequence.push(currentNode);
				currentNode = next;
				currentNode.begin();
			}
		}
		
		return true;
	}
	
	/** Returns true if the sequence was ended. If the sequence
	 * was not running then false is returned. **/
	public boolean end() {
		if (busy) {
			currentNode.end();
			busy = false;
			final boolean all = getBinding(AbstractSequenceBinding.PLAYER) == null;
			new SetGameCameraTargetEvent(this,null,all,true).begin(world);
			for (SequenceListener listener : listeners.toArray(new SequenceListener[0]))
				listener.onSequenceEnd(this);
			currentNode = null;
			input = null;
			localVariables = null;
			bindings.clear();
			sequence.clear();
			return true;
		} else
			return false;
	}
	
	/** Returns true if the sequence is running. **/
	public boolean isBusy() {
		return busy;
	}
	
	/** Returns the current input associated with this sequence. **/
	public Input getInput() {
		return input;
	}
	
	/** Returns the local variable map associated with this sequence. **/
	public  VariableMap getVariables() {
		return localVariables;
	}

}
